Skip to content

5.10 “新类型”模式

在前面我们介绍 trait 的时候,我们提到过孤儿规则(orphan rule),即只要 trait 或类型之一属于 crate 本地(local)的话就可以在此类型上实现该 trait,否则不可以。

我们可以通过“新类型”模式(newtype pattern,newtype 是一个源自 Haskell 编程语言的术语)绕过这个约束,它可以将一个类型包装在一个新的类型中,并为这个新类型实现自己的 Trait、方法和功能。 它可以在一个元组结构体中创建一个新的类型(在介绍结构体的时候我们提到过使用没有命名字段的结构体区分不同类型)。 这个元组结构体包含一个字段作为我们希望为其实现某个 trait 的类型的简单封装。这样这个被封装的类型对于我们的 crate 来说就是本地的了,我们就可以为这个简单的封装实现 trait。

这个模式不会带来任何运行时性能惩罚,在编译时封装类型会被忽略。

例如,在介绍 trait 的章节所提到的如果想要在Vec<T>上实现 Display trait,孤儿规则会阻止我们这么做,因为 Display trait 和Vec<T>都是在标准库中定义的,位于我们的 crate 之外。

利用“新类型”模式我们可以创建一个包含Vec<T>类型的元组结构体类型 Wrapper,然后为 Wrapper 实现 Display trait。参考下面的例子

rs
use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}

因为 Wrapper 是元组结构体而Vec<T>是其于索引位置 0 的元素,所以我们在 Display trait 的实现中通过 self.0 来访问 Wrapper 内部的 Vec<T>。 这样基于 Wrapper 我们就可以使用 Display trait 所定义的功能了。

使用 newtype pattern 有几个好处:

  • 可以使代码更加模块化和可读性更强,因为我们可以将相关的行为集中在新类型的实现中。
  • 可以避免不必要的类型转换和复杂度,也可以提供额外的类型安全性检查,因为新类型与原始类型具有完全不同的语义。
  • 可以使我们更加灵活地管理 Rust 代码中的模块和公共 API,因为我们可以通过添加或删除 newtype 包装器来扩展或收缩接口。

总之,newtype pattern 是一个非常有用的 Rust 设计模式,它可以提高代码的可读性、健壮性和可维护性。

这个模式也有缺点:由于 Wrapper 是一个新类型,它并不包含 Vec<T> 原有的方法;如果要像Vec<T>那样使用 Wrapper,需要为 Wrapper 实现 Vec<T> 的所有方法。 如果希望新类型拥有其内部类型的所有方法,为其实现 Deref trait 并返回其内部类型是一种解决方案(在介绍 Deref trait 的章节我们讨论过)。 如果不希望封装类型拥有所有内部类型的方法则只能手动实现所需的方法(譬如为了限制封装类型的行为)。

Released under the MIT License